/**
	License:		Artistic license 2.0 (see license.txt, also available online at http://www.perlfoundation.org/artistic_license_2_0)
	Authors:		OrbitalLab (http://www.orbitallab.ru/moonglide/), 2007-2009

	File:			GuiBasic.d
	Description:	gui manager - packs rendering data
	Date:			17.05.2008 by Digited
*/
module moonglide.gui.GuiManager;

import
	moonglide.util.MoonUtil,
	moonglide.math.MoonMath,

	moonglide.io.LogSystem,

	moonglide.scenes.IScene,

	moonglide.core.Events,
	moonglide.core.engobjects.EngRawData,

	moonglide.gui.GuiCursor,
	moonglide.gui.GuiControl,
	moonglide.gui.GuiSkin;

//=============================================================
/// CGuiManager - scene object, also the root control of gui tree.
//=============================================================
class CGuiManager : public CGuiControl
{
	protected:
		CGuiCursor	_cursor;

		CGuiControl	_ms_focused,
					_kb_focused;

		CGuiSkin	_currentSkin;

	public:
		CGuiControl[ char[] ]	controls; // hashmap of all controls

		//============================================================
		/// SetSkin - changes gui skin
		//============================================================
		void SetSkin( CGuiSkin skin )
		{
			if( skin is _currentSkin )
			{
				errorLog( "GuiManager: trying to set same skin" );
				return;
			}

			_currentSkin = skin;

			foreach( inout control; controls )
				control.rendStrategy = &skin.Process;
		}

		/* NOTE: why controls "focused" property are set by gui manager rather than by themselves?
		The answer is that it is delegated to gui manager to make possible special situations like dragging and overlapped
			actions of some controls (like "long" drags-changes etc when cursor is affecting a control but
			can be moved far from its borders)
		*/
		void ms_focused( CGuiControl ctrl )							///ditto
		{
			// if same, do nothing.
			if( ctrl !is _ms_focused )
				_ms_focused = ctrl;
		}

		void kb_focused( CGuiControl ctrl )							///ditto
		{
			// if same, do nothing.
			if( ctrl !is _kb_focused )
				_kb_focused = ctrl;
		}

		bool IsPointAbove( Point2f p ) { return true; }		/// always returns true to avoid bugs

		override void update( float frameTime, ref Vector3f parentWPos, ref Matrix33f parentWOrient )
		{
			super.update( frameTime, parentWPos, parentWOrient );

			_cursor.update( frameTime, wposition, worientation );
		}

		//================================================================
		/// event input
		//================================================================
		void onEventCallback( CEvent event, IEventGenerator sender )
		{
			// move cursor, transfer its and kb data to focused controls
			// resize data - resize scalable controls
			switch( event.type )
			{
				case eeventtype.program:
					switch( event.subtype )
					{
						case eprogramevent.resize:
							// resize cursor and root
							_cursor.viewportSize = Size2ui( event.ints["size_x"], event.ints["size_y"] );

							foreach( child; children )
								child.onEventCallback( event, sender );

							return;

						default: break;
					}
					break;

				case eeventtype.system:
					switch( event.subtype )
					{
						case esystemevent.msmove:
							_cursor.Move(); // gets data from moonglide.mouseState - fastest way

							if( _ms_focused !is this )
							{
								_ms_focused.onEventCallback( event, sender );
								return;
							}

							foreach( child; children )
							{
								if( child.isPointAbove( _cursor.Position ))
								{
									// exit - send ms_enter to child under cursor
									_event.clearData();
									_event.type =  eeventtype.gui;
									_event.subtype = eguievent.ms_enter;
									child.onEventCallback( _event, sender );
									return;
								}
							}

							if( _ms_focused )
							{
								// hover
								_event.clearData();
								_event.type =  eeventtype.gui;
								_event.subtype = eguievent.ms_hover;
								generateEvent( _event );
							}
							else
							{
								// enter
								_event.clearData();
								_event.type =  eeventtype.gui;
								_event.subtype = eguievent.ms_enter;
								generateEvent( _event );
							}
							return;
					}
					break;

				case eeventtype.gui:
					switch( event.subtype )
					{
						case eguievent.scaling:	// nothing to scale
							foreach( child; children )
								child.onEventCallback( event, sender );
							return;

						case eguievent.created:
							// control is created - must add to parent and give skin rendering strategy
							static CGuiControl control;
							control = cast( CGuiControl )sender;
							debug moonAssert( control !is null, "guiControl cast failed", __FILE__, __LINE__ );

							controls[ control.id ] = control;

							scope( exit ) _currentSkin.onCreate( control );

							if( control.strings[ "parent_id" ].length > 0 )
							{
								static CGuiControl* result;
								result = control.strings[ "parent_id" ] in controls;

								if( result !is null )
								{
									// register control as child of found parend
									(*result).addChild( control );
									return;
								}
							}

							addChild( control );
							return;

						case eguievent.destroyed:
							// destroyed - remove from parent
							return;

						default: break;
					}
					break;

				default: break;
			}

			super.onEventCallback( event, sender );
		}

		void onEventReceive( CEvent event, IEventSender sender ) {}

//================================================================
/// constructor & destructor stuff
//================================================================
		this( Size2ui viewport, CGuiSkin skin )
		in
		{
			moonAssert( skin !is null, "skin is null", __FILE__, __LINE__ );
			moonAssert(( viewport.x != 0 ) && ( viewport.y != 0 ), "viewport has 0", __FILE__, __LINE__ );
		}
		body
		{
			super( "guiRootManager" );

			_currentSkin = skin;
			_ms_focused = _kb_focused = this;

			_cursor = new CGuiCursor( "guiCursor", viewport );
			_currentSkin.onCreate( _cursor );
		}


		~this() 		///ditto
		{
			foreach( inout child; children )
				delete child;
		}
}
